extern crate core;

use std::sync::atomic::AtomicBool;
use std::mem::transmute;
use std::sync::atomic::Ordering;

pub struct Observer {
    hidden: Option<*const HiddenState>
}

pub struct Observable {
    hidden: Option<*const HiddenState>
}

struct HiddenState {
    observer: AtomicBool,
    observable: AtomicBool,
}

impl Observable {
    pub fn new() -> (Observer, Observable) {
        let state = Box::new(HiddenState {
            observable: AtomicBool::new(true),
            observer: AtomicBool::new(true),
        });

        let raw_state: *const HiddenState;
        unsafe {
            // This pointer is now an indefinitely valid static reference.
            raw_state = transmute(state);
        }

        let observer = Observer { hidden: Some(raw_state) };
        let observable = Observable { hidden: Some(raw_state) };
        return (observer, observable);
    }

    // This is safe because no other mutable references can exist of HiddenState::observable
    fn atomic_state(&mut self) -> &mut AtomicBool {
        unsafe {
            let state_ref = self.immutable_state();
            let atomic_state_ref = &state_ref.observable as *const AtomicBool;
            return &mut (*(atomic_state_ref as *mut AtomicBool));
        }
    }

    // This is safe because it returns an immutable reference
    fn immutable_state(&mut self) -> &HiddenState {
        unsafe {
            let state_ref = &(**(self.hidden.as_ref().unwrap()));
            return state_ref;
        }
    }
}

impl Observer {
    // This is safe because no other mutable references can exist of HiddenState::observer
    fn atomic_state(&mut self) -> &mut AtomicBool {
        unsafe {
            let state_ref = self.immutable_state();
            let atomic_state_ref = &state_ref.observer as *const AtomicBool;
            return &mut (*(atomic_state_ref as *mut AtomicBool));
        }
    }

    // This is safe because it returns an immutable reference
    fn immutable_state(&mut self) -> &HiddenState {
        unsafe {
            let state_ref = &(**(self.hidden.as_ref().unwrap()));
            return state_ref;
        }
    }
}

impl HiddenState {
    fn should_drop(&self) -> bool {
        return !self.observer.fetch_or(self.observable.load(Ordering::Acquire), Ordering::Acquire);
    }
}

impl Drop for Observable {
    fn drop(&mut self) {
        self.atomic_state().store(false, Ordering::Acquire);
        if self.immutable_state().should_drop() {
            unsafe {
                transmute::<*const HiddenState, Box<HiddenState>>(self.hidden.take().unwrap());
            }
        }
    }
}

impl Drop for Observer {
    fn drop(&mut self) {
        self.atomic_state().store(false, Ordering::Acquire);
        if self.immutable_state().should_drop() {
            unsafe {
                transmute::<*const HiddenState, Box<HiddenState>>(self.hidden.take().unwrap());
            }
        }
    }
}
